#ifndef OPERATORS_H
#define OPERATORS_H

#include <cmath>

/*** UNARY OPERATORS ***/

class NegateOperator {
	public:
		template <typename a_t, typename out_t>
		out_t compute(a_t a) {
			return -a;
		}

		Type outType(Type inType) {
			assert(inType != BOOL);
			return inType;
		}
};

class LogOperator {
	public:
		template <typename a_t, typename out_t>
		out_t compute(a_t a) {
			return log((double) a);
		}

		Type outType(Type inType) {
			assert(inType != BOOL);
			return DOUBLE;
		}
};

class NotOperator {
	public:
		template <typename a_t, typename out_t>
		out_t compute(a_t a) {
			return !a;
		}

		Type outType(Type inType) {
			assert(inType == BOOL);
			return BOOL;
		}
};

/*** BINARY OPERATORS ***/

class AddOperator {
	public:
		template <typename a_t, typename b_t, typename out_t>
		out_t compute(a_t a, b_t b) {
			return a + b;
		}

		Type outType(Type inType1, Type inType2) {
			assert(inType1 != BOOL && inType2 != BOOL);

			if (inType1 == DOUBLE || inType2 == DOUBLE)
				return DOUBLE;
			else
				return INT;
		}
};

class SubtractOperator {
	public:
		template <typename a_t, typename b_t, typename out_t>
		out_t compute(a_t a, b_t b) {
			return a - b;
		}

		Type outType(Type inType1, Type inType2) {
			assert(inType1 != BOOL && inType2 != BOOL);

			if (inType1 == DOUBLE || inType2 == DOUBLE)
				return DOUBLE;
			else
				return INT;
		}
};

class MultiplyOperator {
	public:
		template <typename a_t, typename b_t, typename out_t>
		out_t compute(a_t a, b_t b) {
			return a * b;
		}

		Type outType(Type inType1, Type inType2) {
			assert(inType1 != BOOL && inType2 != BOOL);

			if (inType1 == DOUBLE || inType2 == DOUBLE)
				return DOUBLE;
			else
				return INT;
		}
};

class FloatingDivideOperator {
	public:
		template <typename a_t, typename b_t, typename out_t>
		out_t compute(a_t a, b_t b) {
			return (double) a / (double) b;
		}

		Type outType(Type inType1, Type inType2) {
			assert(inType1 != BOOL && inType2 != BOOL);
			return DOUBLE;
		}
};

class LowerOperator {
	public:
		template <typename a_t, typename b_t, typename out_t>
		out_t compute(a_t a, b_t b) {
			return a < b;
		}

		Type outType(Type inType1, Type inType2) {
			return BOOL;
		}
};

class GreaterOperator {
	public:
		template <typename a_t, typename b_t, typename out_t>
		out_t compute(a_t a, b_t b) {
			return a > b;
		}

		Type outType(Type inType1, Type inType2) {
			return BOOL;
		}
};

class EqualOperator {
	public:
		template <typename a_t, typename b_t, typename out_t>
		out_t compute(a_t a, b_t b) {
			return a == b;
		}

		Type outType(Type inType1, Type inType2) {
			return BOOL;
		}
};

class NotEqualOperator {
	public:
		template <typename a_t, typename b_t, typename out_t>
		out_t compute(a_t a, b_t b) {
			return a != b;
		}

		Type outType(Type inType1, Type inType2) {
			return BOOL;
		}
};

class OrOperator {
	public:
		template <typename a_t, typename b_t, typename out_t>
		out_t compute(a_t a, b_t b) {
			return a || b;
		}

		Type outType(Type inType1, Type inType2) {
			assert(inType1 == BOOL && inType2 == BOOL);
			return BOOL;
		}
};

class AndOperator {
	public:
		template <typename a_t, typename b_t, typename out_t>
		out_t compute(a_t a, b_t b) {
			return a && b;
		}

		Type outType(Type inType1, Type inType2) {
			assert(inType1 == BOOL && inType2 == BOOL);
			return BOOL;
		}
};

#endif
